--[[
Sol - fog calculation modul
Author: Peter Boese

The basic model:

Cam position       material    mist                    pollusion                                  humidity                 distant

|                  |           |                       |                                          |                        |   
|                  |           |                       |                                          |                        |   
                   |           |                       |                                          |                        |   
O                  |           |                       |                                          |                        |   
|                  |           |                       |                                          |                        |           
###################################################################################################################################
Mix:            variable    variable                variable                                   variable                   100%
Mix hierarchy      1           2                       3                                          4                        5

Fog values are interpolated by modulators like:

__humidity : humidity strenght
__smog : main smog value, generated by track adaptions / sky__smog : smog multiplier in sol_config
__fog_dense : mist strenght
__inair_material.dense : dense of the inair material

to create a resulting fog.


]]

-- distance fog, higher absorbtion of low frequencies
local _l_dist_fog = {

	distance 	= 30000, -- the distance in meters
	blend 		= 0.90, -- at the full distance, the fog is mixed by this value
	density 	= 1.75, -- a multiplier for the mixing
	exponent 	= 1.20, -- lower values will make the near fog denser
	backlit 	= 0.05, -- fog is litten from behind by the sun
	sky 		= 0.0, -- maximum pollution of the sky by this particular fog
	sun			= 1.0,
	night 		= 0.0, -- an offset to be more visible in the night
	color 		= hsv(220, 0.5, 2.5):toRgb() -- the fog's color, HSV model
	--[[
	HSV Model:
	H: Hue (0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 315=magenta)
	S: Saturation
	V: Value
	]]
}

-- humidity fog, light is absorbed by humid air in the distant 
-- can be used also for diffusing horizontal line
local _l_humidity_fog = {

	distance = 18000,
	blend = 1.00,
	density = 2.0,
	exponent = 1.0,
	backlit = { 0.60, 0.05 }, -- { value with zero dense, value with full dense }
	sky = 0.0,
	night = 0.25,
	color_low = hsv(215, 0.45, 6):toRgb(),
	color_high = hsv(215, 0.65, 4.0):toRgb()
}

-- smog fog, light is absorbed by pollusion
local _l_smog_fog = {

	distance = 20000,
	blend = 1.7,
	density = 1.5,
	exponent = 0.45,
	backlit = { 0.1, 0.001 },
	sky = 0.9,
	night = 0.5,
	color_low = hsv(215, 0.15,  25):toRgb(),
	color_high = hsv(225, 0.25, 2.50):toRgb()
}

-- mist fog, ligth is absorbed by steam, steam is litten by the sun 
local _l_mist_fog = {

	distance = 1700,
	blend = 1.35,
	density = 2.0,
	exponent = 0.4,
	backlit = { 0.7, 0.01 },
	sky = 1.0,
	night = 0.5,
	color_low = hsv(225, 0.20, 16.00):toRgb(),
	color_high = hsv(225, 0.00, 3.50):toRgb()
}

-- material filled air, light is absorbed by a material in the air
local _l_material_fog = {

	distance = 1500,
	blend = 1.2,
	density = 1.0,
	exponent = 0.3,
	backlit = { 0.1, 0.001 },
	sky_low = 0.10,
	sky_high = 1.00,
	night = 0.125,
	color = hsv(225, 0.0, 1.0):toRgb()
}


-- A simple LUT to control distant and smog fog while transition to night    
local _l_sunangle_multi_LUT = {
	     -- gain,    
	{ -90,  0.00 },
	{ -18,  0.00 },
	{ -12,  0.07 },
	{  -6,  0.15 },
	{  -3,  0.20 },
	{   0,  0.25 },
	{   3,  0.35 },
	{   6,  0.50 },
	{   9,  0.65 },
	{  12,  0.80 },
	{  20,  1.00 },
	{  90,  1.00 },
}
local _l_SunangleMulti_lut
local _l_SunangleMultiCPP = LUT:new(_l_sunangle_multi_LUT)

-- basic temperature dependency curve
-- Index is temperature
local _l_fog_temp_LUT = {
            -- dist  humi  mist  level       
	{    0,    1.05, 1.10, 0.70, 1.50   },
	{    5,    1.04, 1.08, 1.85, 1.20   },
	{   10,    1.03, 1.06, 1.20, 0.75   },
	{   17,    1.02, 1.04, 1.00, 0.90   },
	{   24,    1.00, 1.00, 1.00, 1.00   },
	{   32,    1.03, 1.05, 1.00, 1.10   },
	{   40,    1.20, 1.15, 1.00, 1.20   },
}
local _l_FogTemp_lut
local _l_FogTempCPP = LUT:new(_l_fog_temp_LUT)
local _l_mist = 0


-- a little LUT to translate the real humidity into the linear technical humidity fog
local _l_humidity_LUT = {
                       --          
	{  0.00,   0.00,  },
	{  0.10,   0.01,  },
	{  0.20,   0.03,  },
	{  0.30,   0.06,  },
	{  0.40,   0.11,  },
	{  0.50,   0.15,  },
	{  0.60,   0.20,  },
	{  0.70,   0.35,  },
	{  0.80,   0.45,  },
	{  0.90,   0.70,  },
	{  1.00,   1.00,  },
}
local _l_humid = 0
local _l_humidCPP = LUT:new(_l_humidity_LUT)

local _l_smog = 0

local light_distance_filter = rgb(0,0,0)

local f_dist = 40000
local f_blend = 1.0
local f_exponent = 1.0

local _l_FOG_COLOR = hsv(0,0,1)
function GFX__get_fog_color_HSV()
	return hsv(_l_FOG_COLOR.h,_l_FOG_COLOR.s,_l_FOG_COLOR.v)
end
function GFX__set_fog_color_HSV(color)
	ac.setFogColor( color:toRgb() )
end

-- main function to retrieve the fog mix value by a given distance
function gfx__get_fog_dense(radius)

	return math.pow((1-((f_dist-radius)/math.max(1,f_dist)))*f_blend, f_exponent * (1 + __inair_material.granulation))
end

function gfx__get_smog_dense()

	if __CW__.CustomWeather__.use then 

		return __CW__.CustomWeather__Pollutions.Smog
	else
		return math.min(1, __smog * SOL__config("sky", "smog"))
	end
end

function gfx__get_humidity()

	return _l_humid
end

function gfx__get_distance_spectral_filter()

	return light_distance_filter
end

function gfx__get_custom_distant_fog()
	
	return _l_dist_fog
end

function gfx__set_custom_distant_fog( custom_distant_fog )
	
	_l_dist_fog = {

		distance 	= custom_distant_fog.distance ,
		blend 		= custom_distant_fog.blend ,
		density 	= custom_distant_fog.density ,
		exponent 	= custom_distant_fog.exponent ,
		backlit 	= custom_distant_fog.backlit ,
		sky 		= custom_distant_fog.sky ,
		sun			= custom_distant_fog.sun ,
		night 		= custom_distant_fog.night ,
		color 		= custom_distant_fog.color ,
	}

	--compensate custom distant fog with other fog inputs
	local comp_sky = math.min(1, math.max( _l_humid, math.max(_l_smog, __inair_material.dense * 1.75)))
	_l_dist_fog.sky 		= math.lerp(_l_dist_fog.sky, 0, comp_sky)
	_l_dist_fog.exponent 	= math.lerp(_l_dist_fog.exponent, 1, comp_sky)
	_l_dist_fog.color 	= math.lerp(_l_dist_fog.color, hsv(220, 0.5, 2.5):toRgb(), comp_sky)
end


function gfx__update_fog()

	local _l_night_slow_fade = __IntN(0,1,10)

	if SOL__config("nerd__fog_custom_distant_fog", "use") then

		local _l__custom_distant_fog = {

			distance 	= SOL__config("nerd__fog_custom_distant_fog", "distance") ,
			blend 		= SOL__config("nerd__fog_custom_distant_fog", "blend") ,
			density 	= SOL__config("nerd__fog_custom_distant_fog", "density") ,
			exponent 	= SOL__config("nerd__fog_custom_distant_fog", "exponent") ,
			backlit 	= SOL__config("nerd__fog_custom_distant_fog", "backlit") ,
			sky 		= SOL__config("nerd__fog_custom_distant_fog", "sky") ,
			sun 		= SOL__config("nerd__fog_custom_distant_fog", "sun") ,
			night 		= SOL__config("nerd__fog_custom_distant_fog", "night") ,
			color = hsv(SOL__config("nerd__fog_custom_distant_fog", "Hue"), 
						SOL__config("nerd__fog_custom_distant_fog", "Saturation"), 
						SOL__config("nerd__fog_custom_distant_fog", "Level")    ):toRgb() -- the fog's color, HSV model
		}
		gfx__set_custom_distant_fog(_l__custom_distant_fog)
	end

	-- modulate ambient light with temperature
	_l_FogTemp_lut 	= _l_FogTempCPP:get(__temperature) --interpolate__plan(_l_fog_temp_LUT, nil, __temperature)

	-- translate humidity
	_l_humid = (_l_humidCPP:get(__humidity) or {0,0})[1] --(interpolate__plan(_l_humidity_LUT, nil, __humidity) or {0,0})[1]
	
	-- translate fog/mist
	_l_mist = math.pow(__fog_dense, 2)

	-- main smog calculation
	_l_smog = math.pow(gfx__get_smog_dense(), 1.25)
	-- lower distance with high smog values
	_l_smog_fog.distance = 25000-20000*math.pow(_l_smog, 2)

	local _l_granulation_ratio = math.pow(1-__inair_material.granulation, 0.1)
	local _l_humidity_ratio = math.pow(_l_humid * _l_FogTemp_lut[2], 1) * _l_night_slow_fade

	

	--compensate mist distant look with negative distant.sky
	--_l_mist_fog.sky = 0.15 * math.pow(math.max(0, _l_dist_fog.sky*-1), 0.35)



	-- create material fog color and make it dependent to ambient and sun ligth
	_l_material_fog.color = hsv(__inair_material.color.h,
								__inair_material.color.s,
								__inair_material.color.v * math.pow(__ambient_color.v * 0.05 + #__lightColor * 0.025, 0.7)):toRgb()

	local PPoff_fog_color_gain = 1
	if nopp__use_sol_without_postprocessing then 
		--compensate ambient brightness calculations with PPoff adjustments
		PPoff_fog_color_gain = 0.75
	end


	--###  distance  ###
	_l_dist_fog.distance = math. min(_l_dist_fog.distance, ta_dome_size)
	f_dist = _l_dist_fog.distance * ta_fog_distance
		
	f_dist = math.lerp( f_dist, math.min(f_dist, _l_humidity_fog.distance), _l_humidity_ratio)
	f_dist = math.lerp( f_dist, math.min(f_dist, _l_smog_fog.distance), 	_l_smog)
	f_dist = math.lerp( f_dist, math.min(f_dist, _l_mist_fog.distance * _l_FogTemp_lut[3]), 	math.pow(_l_mist, 0.5))
	f_dist = math.lerp( f_dist, math.min(f_dist, _l_material_fog.distance), __inair_material.dense)
	
	ac.setFogDistance( f_dist ) 


	--###  blend  ###
	f_blend = _l_dist_fog.blend * ta_fog_blend * _l_FogTemp_lut[1]
	_l_material_fog.blend = 1.15 * _l_granulation_ratio
		
	f_blend = math.lerp( f_blend, math.max(f_blend, _l_humidity_fog.blend), _l_humidity_ratio)
	f_blend = math.lerp( f_blend, math.max(f_blend, _l_smog_fog.blend), 	_l_smog)
	f_blend = math.lerp( f_blend, math.max(f_blend, _l_mist_fog.blend), 	_l_mist)
	f_blend = math.lerp( f_blend, math.max(f_blend, _l_material_fog.blend * _l_granulation_ratio), __inair_material.dense)
	
	ac.setFogBlend( f_blend ) 


	--###  density  ###
	local f_density = _l_dist_fog.density * (1 + 0.2 * (__overcast + __badness) * from_twilight_compensate(0))
		
	f_density = math.lerp( f_density, math.max(f_density, _l_humidity_fog.density), _l_humidity_ratio)
	f_density = math.lerp( f_density, math.max(f_density, _l_smog_fog.density), 	_l_smog)
	f_density = math.lerp( f_density, math.max(f_density, _l_mist_fog.density), 	_l_mist)
	f_density = math.lerp( f_density, math.max(f_density, _l_material_fog.density), __inair_material.dense)

	ac.setFogDensity( f_density ) 


	--###  exponent  ###
	f_exponent = _l_dist_fog.exponent
		
	f_exponent = math.lerp( f_exponent, _l_humidity_fog.exponent,	_l_humidity_ratio)
	f_exponent = math.lerp( f_exponent, _l_smog_fog.exponent, 		_l_smog)
	f_exponent = math.lerp( f_exponent, _l_mist_fog.exponent, 		_l_mist)
	f_exponent = math.lerp( f_exponent, _l_material_fog.exponent, 	__inair_material.dense)
	
	ac.setFogExponent( f_exponent ) 


	--###  height  ###
	ac.setFogHeight(8000)



	--###  color  ###
	_l_SunangleMulti_lut = _l_SunangleMultiCPP:get() --interpolate__plan(_l_sunangle_multi_LUT)
	local pollute_fog = hsv(__light_pollution.h,
							__light_pollution.s * 4 * _l_mist,
							__light_pollution.v
							* (1-0.65*__extern_illumination_offset))--__extern_illumination


	local f_sun_level = _l_SunangleMulti_lut[1]

	local _l_overcast_blue = math.max(0, math.min(1, (-0.80+__overcast)*5))

	local _l_temp_dist_color = _l_dist_fog.color:toHsv()
	_l_temp_dist_color.s =  _l_temp_dist_color.s * (1 - (0.25-0.5*_l_overcast_blue)*__overcast + (-0.25-0.15*__overcast)*__badness)
	_l_temp_dist_color.v = _l_temp_dist_color.v * math.max(0, (1 + (0.5-0.5*_l_overcast_blue)*__overcast - (0.3 + 1.00 * __overcast)*__badness ))

	local _l_night_distance_damper = (1-from_twilight_compensate(0)) * (1-(__extern_illumination_radius/20000)) --* math.min(1, __light_pollution.v*5)
	_l_night_distance_damper = math.lerp(_l_night_distance_damper, 1, math.min(1,_l_mist*1.5))

	local f_color = _l_temp_dist_color:toRgb() * ta_fog_level * f_sun_level
					--- __IntD(0.4 + 0.1*__night__brightness_adjust + math.lerp(__moonlight_color.v*day_compensate(12), 0, __night__brightness_adjust*0.75), 0, 20) -- darken distance fog more to mask litten area 
					-- _l_night_distance_damper
	--f_color = math.lerp( __ambient_color:toRgb()*-0.75/math.max(1,__light_pollution.v*0.5)*(0.5+0.5*_l_night_distance_damper), f_color, day_compensate(0))
	f_color = math.lerp((-2*(0.20+0.80*_l_night_distance_damper)+__night__brightness_adjust)+__ambient_color:toRgb()*-0.5/math.max(1,__light_pollution.v*2), f_color, day_compensate(0))	
	
	-- measure the sky's color and add it to the different fogs - most with low sunangles
	local sky_color_calculation_point = temp_interpol(25,1.25,1.0) * interpolate_day_time(6.5,5.0,6.0)  
	local measured_sky_color = gfx__get_sky_color(sphere2vec3(__sun_heading, sky_color_calculation_point), true)
					   		 * temp_interpol(25,1.00,0.85)
					   		
	measured_sky_color = measured_sky_color:toHsv()	
	measured_sky_color.s = measured_sky_color.s * 5
	measured_sky_color = measured_sky_color:toRgb()			
	
	local f_mix = (1-0.75*sun_compensate(0))
	f_color = math.lerp(f_color, measured_sky_color, 0.25 * f_mix * math.pow(_l_humidity_ratio, 0.5))
	local _l_temp_humid_color = math.lerp(_l_humidity_fog.color_low, _l_humidity_fog.color_high, _l_humidity_ratio)
	_l_temp_humid_color = math.lerp(_l_temp_humid_color, measured_sky_color, 0.3 * f_mix * (1-0.75*_l_humidity_ratio))

	local _l_smog_color_calc  = math.lerp(_l_smog_fog.color_low, _l_smog_fog.color_high, math.pow(_l_smog, 0.33))
	local _l_temp_smog_color  = math.lerp(_l_smog_color_calc,    measured_sky_color, 0.7 * f_mix * (1-0.75*_l_smog))
	
	local _l_mist_color_calc  = math.lerp(_l_mist_fog.color_low, _l_mist_fog.color_high, _l_mist)
	local _l_temp_mist_color  = math.lerp(_l_mist_color_calc, 	 measured_sky_color, 0.5 * f_mix * (1-0.75*_l_mist))
	----------------------------------------------------------------------------------------------------------------------------



	f_color = math.lerp(f_color, _l_temp_humid_color	* math.max(f_sun_level, _l_humidity_fog.night), _l_humidity_ratio)
	f_color = f_color * _l_FogTemp_lut[4]

	f_color = math.lerp(f_color, _l_temp_smog_color     * math.max(f_sun_level, _l_smog_fog.night    ), _l_smog)
	
	f_color = math.lerp(f_color, (_l_temp_mist_color	* math.max(f_sun_level, _l_mist_fog.night*_l_mist)   ):add(pollute_fog:toRgb()), 	math.pow(_l_mist, 0.5))
	f_color = f_color * math.pow(__ambient_color.v * 0.04 + #__lightColor * 0.02, 0.7) * (1-0.5*__overcast)
	
	local _l_sun_light_add = 1
	if _l_dist_fog and _l_dist_fog.sun then
		_l_sun_light_add = _l_dist_fog.sun
	end
	f_color:add(__lightColor*0.08*_l_sun_light_add)
	f_color = f_color * math.max(0, 1-#__lightColor*0.02*math.pow(_l_sun_light_add, 0.33))
	--f_color = f_color * night_compensate(sun_compensate(1-math.max(0.5*_l_smog, 0.0*_l_humidity_ratio)))

	f_color = math.lerp(f_color, (_l_material_fog.color	* math.max(f_sun_level, _l_material_fog.night)):add(pollute_fog:toRgb() * (#_l_material_fog.color*sun_compensate(0.1))), 	__inair_material.dense)

	f_color = f_color * __PPoff__brightness__regulation * PPoff_fog_color_gain

	f_color = f_color * (1-0.7*__badness*(1-0.9*__overcast))
	f_color:adjustSaturation((1+0.25*__badness))
	--f_color:adjustSaturation(0)

	ac.setFogColor( f_color )
	_l_FOG_COLOR = f_color:toHsv()

	--ac.debug("####", pollute_fog:toRgb())


	--###  backlit  ###
	local f_backlit = _l_dist_fog.backlit * (1 - 0.75 * sun_compensate(0))
		
	f_backlit = math.lerp( f_backlit, math.lerp(_l_humidity_fog.backlit[1], _l_humidity_fog.backlit[2], _l_humidity_ratio),	__humidity *__IntD(1, 0.25, 0.6))
	f_backlit = math.lerp( f_backlit, math.lerp(_l_smog_fog.backlit[1], 	_l_smog_fog.backlit[2], 	_l_smog),	_l_smog)
	f_backlit = math.lerp( f_backlit, math.lerp(_l_mist_fog.backlit[1], 	_l_mist_fog.backlit[2], 	_l_mist),	_l_mist)
	f_backlit = math.lerp( f_backlit, 
						   math.lerp(_l_material_fog.backlit[1], _l_material_fog.backlit[2], __inair_material.dense) * #_l_material_fog.color,
						   __inair_material.dense)

	ac.setFogBacklitMultiplier(f_backlit * (math.pow(SOL__config("sun", "fog_bloom"), 2) * 0.5)) 
	ac.setFogBacklitExponent(4-3*f_backlit)

	--###  sky fog  ###
	__sky_fog = _l_dist_fog.sky
		
	__sky_fog = math.lerp( __sky_fog, math.max(__sky_fog, _l_humidity_fog.sky), _l_humidity_ratio * math.max(0, 1-2*_l_smog))
	local _l_humidity_top_clearer = 6 * math.max(0, 1-2*__inair_material.dense)
	local _l_sky__fog_calc = math.lerp( 0-_l_humidity_top_clearer*math.pow(_l_humidity_ratio, 3), __sky_fog, math.min(1, math.abs(__sky_fog)))
	__sky_fog = math.lerp(__sky_fog, _l_sky__fog_calc, sun_compensate(0) * math.max(0, (1-1.5*_l_mist)))

	__sky_fog = math.lerp( __sky_fog, math.max(__sky_fog, _l_smog_fog.sky), 	math.pow(_l_smog, 1))
	__sky_fog = math.lerp( __sky_fog, math.max(__sky_fog, _l_mist_fog.sky), 	math.pow(_l_mist, 4))
	
	local _l_material_sky_mix = math.lerp(_l_material_fog.sky_low, _l_material_fog.sky_high, __inair_material.dense)
	__sky_fog = math.lerp( __sky_fog, math.max(__sky_fog, _l_material_sky_mix), __inair_material.dense)
	


	
	--local _l_sky__fog_calc = math.lerp( 0-7*math.pow(_l_humidity_ratio, 3), __sky_fog, math.min(1, math.abs(__sky_fog)))
	--_d(_l_sky__fog_calc)
	ac.setSkyFogMultiplier(__sky_fog * (1-0.8*__inair_material.granulation))


	--###  horizont fog  ###
	local f_hori = 0.4--don't start with 0
	
	f_hori = math.max( f_hori, math.pow(_l_humidity_ratio, 0.3) * 0.95)
	f_hori = math.max( f_hori, math.pow(_l_mist, 0.5) * 4)
	f_hori = math.max( f_hori, math.pow(_l_smog , 0.2) * 1.05)
	f_hori = f_hori * _l_FogTemp_lut[1]
	f_hori = math.max( f_hori, math.pow(__inair_material.dense , 0.33) * 1.10)
	f_hori = math.min(1, f_hori)
	
	local h_fog
	local h_fog_exp
	local h_fog_height

	local h_smog_mod = math.pow(_l_smog, 0.5)
	local h_material_mod = math.pow(__inair_material.dense, 0.3)

	h_fog = f_hori * math.lerp(_l_night_distance_damper, 1, _l_night_slow_fade) * (1 + 0.25*__overcast*day_compensate(0))
	h_fog = math.pow(h_fog, 1.5)
	h_fog_exp 	 = 0.30 + math.max(1.5*h_material_mod , math.max(1.0*h_smog_mod , math.max(1.00*_l_mist, 0.00*_l_humidity_ratio)))
	h_fog_height = 1.15 - math.max(0.2*h_material_mod , math.max(0.4*h_smog_mod , math.max(0.40*__fog_dense, 0.20*_l_humidity_ratio)))


	ac.setHorizonFogMultiplier(h_fog, h_fog_exp, h_fog_height)





	-- calculate a filter for the lights travel through air/polluted air
	local spectrum_filter__smog  = math.pow(_l_smog, 2)
	local spectrum_filter__humid = _l_humidity_ratio
	light_distance_filter = hsv(205 - math.min(25*spectrum_filter__smog, 40*spectrum_filter__humid),
								  (1 - 0.25*spectrum_filter__smog),
								  3 + math.max(23*spectrum_filter__smog, 6*spectrum_filter__humid)):toRgb()
	light_distance_filter = light_distance_filter * sun_compensate(0) * (1 - __overcast)
end